//#include "stdafx.h"
#include "Core/Commons.h"

#if defined(_WIN32)
	#include <windows.h>
	#include <sys/timeb.h>
	#include <math.h>
#elif defined(IPHONEOS)
	#include "sys/time.h"
	#include <mach/mach.h> 
	#include <mach/mach_time.h> 
#endif
#include "profile.h"
#include <stdio.h>

#ifndef GX_SPRINTF
#if defined(WIN32)
#define GX_SPRINTF sprintf_s
#else
#define GX_SPRINTF sprintf
#endif
#endif

ProfileManager::ProfileManager()
{
	enable = false;
	groupMask = -1;
	numericalFilter = 0.f;
	for (int i = 0; i < kNumProfileSamples; ++i) {
		sSamples[i].isValid = false;
		sSamples[i].profileGroup = -1;
		sHistory[i].isValid = false;
		sHistory[i].profileGroup = -1;
		sGroups[i][0] = 0;
	}
	sStartProfileTime = currentMicroSeconds();
	sEndProfileTime = currentMicroSeconds();
	clearTextBuffer();
}

ProfileManager::~ProfileManager()
{
}

unsigned long ProfileManager::currentMicroSeconds()
{
	const bool kHighPerformance = !true;
#if defined(_WIN32)
	static LARGE_INTEGER sFrequency;
	static BOOL isInit = QueryPerformanceFrequency( &sFrequency );
	static double sMicroSecondsPerTick = (1.0 / sFrequency.QuadPart) * 1e+6;
	LARGE_INTEGER co;

	if (kHighPerformance) {
		DWORD dwLow;
		DWORD dwHeight;
		__asm {
			rdtsc
			mov dwLow, eax
			mov dwHeight, edx
		}
		co.LowPart = dwLow;
		co.HighPart = dwHeight;
		//co.QuadPart = ((unsigned __int64)dwHeight << 32) | (unsigned __int64)dwLow;
	} else {
		QueryPerformanceCounter( &co );
	}
	static unsigned __int64 sSystemStartTime = co.QuadPart;
	return unsigned long(((co.QuadPart - sSystemStartTime) * sMicroSecondsPerTick + 0.5)); 

#elif defined(__APPLE__)
	if (kHighPerformance) {
		static mach_timebase_info_data_t sInfo;
		static bool isInit = mach_timebase_info(&sInfo);
		static double sMicroSecondsPerTick =  1e-3 * ((float)sInfo.numer / (float)sInfo.denom);
		static uint64_t sSystemStartTime = mach_absolute_time();
		return (unsigned long)((mach_absolute_time() - sSystemStartTime) * sMicroSecondsPerTick + 0.5);
	} else {
		timeval t;
		gettimeofday(&t, NULL);
		static unsigned long sSystemStartTime = t.tv_sec;
		return (unsigned long)((t.tv_sec - sSystemStartTime) * 1e+6 + t.tv_usec);
	}
#endif
}

float ProfileManager::toSeconds(unsigned long microseconds)
{
	return microseconds * 1e-6f;
}

bool ProfileManager::findProfileSample(const char* name, SProfileSample** sample)
{
	*sample = NULL;
	int index = 0;
	while(index < kNumProfileSamples && sSamples[index].isValid) {
		if (strcmp(sSamples[index].szName, name) == 0) {
			*sample = &sSamples[index];
			return true;
		}
		index++;
	}
	if (index >= kNumProfileSamples) {
		*sample = NULL;
		return false;
	} else {
		*sample = &sSamples[index];
		return false;
	}
}

bool ProfileManager::findProfileSampleHistory(const char* name, SProfileSampleHistory** history)
{
	*history = NULL;
	int index = 0;
	while(index < kNumProfileSamples && sHistory[index].isValid) {
		if (strcmp(sHistory[index].szName, name) == 0) {
			*history = &sHistory[index];
			return true;
		}
		index++;
	}
	if (index >= kNumProfileSamples) {
		*history = NULL;
		return false;
	} else {
		*history = &sHistory[index];
		return false;
	}
}

int ProfileManager::findGroupId(const char* name)
{
	int index = 0;
	while (index < kNumProfileSamples && sGroups[index][0] != '\0') {
		if (strcmp(sGroups[index], name) == 0) {
			return index;
		}
		index++;
	}
	if (index >= kNumProfileSamples) {
		ASSERT(!"Exceeded Max Available Profile Groups");
		return -1;
	} else {
		strcpy(sGroups[index], name);
		return index;
	}
}
void ProfileManager::setEnable(bool b)
{
	enable = b;
}

bool ProfileManager::isEnable()
{
	return enable;
}

void ProfileManager::profileBegin(const char* group, const char* name)
{
	if (!enable) {
		return;
	}
	SProfileSample* sample = NULL;
	bool found = findProfileSample(name, &sample);
	if (found) {
		sample->openProfiles++;
		sample->profileInstances++;
		sample->startTime = currentMicroSeconds();
		ASSERT(sample->openProfiles == 1 && "openProfiles should be 1");
	} else if (sample) {
		int groupId = findGroupId(group);
		sample->profileGroup = groupId;
		strcpy(sample->szName, name);
		sample->isValid = true;
		sample->openProfiles = 1;
		sample->profileInstances = 1;
		sample->accumulatorTime = 0;
		sample->childrenSampleTime = 0;
		sample->startTime = currentMicroSeconds();
	} else {
		ASSERT(!"Exceeded Max Available Profile Samples");
	}
}

void ProfileManager::profileEnd(const char* name)
{
	if (!enable) {
		return;
	}
	unsigned long endTime = currentMicroSeconds();

	SProfileSample* sample = NULL;
	bool found = findProfileSample(name, &sample);
	if (found) {
		sample->openProfiles--;
		int inner = 0;
		int parent = -1;
		int numParents = 0;
		while(sSamples[inner].isValid) {
			if (sSamples[inner].openProfiles > 0) {
				numParents++;
				if(parent < 0) {
					parent = inner;
				} else if (sSamples[inner].startTime >= sSamples[parent].startTime) {
					parent = inner;
				}
			}
			inner++;
		}
		sample->numParents = numParents;
		if (parent >= 0) {
			sSamples[parent].childrenSampleTime += toSeconds(endTime - sample->startTime);
		}
		sample->accumulatorTime += toSeconds(endTime - sample->startTime);
	}
}

void ProfileManager::resetAll()
{
	for (int i = 0; i < kNumProfileSamples; i++) {
		sSamples[i].isValid = false;
	}
}

SProfileSampleHistory* ProfileManager::storeProfileInHistory(int groupId, const char* name, float sampleTime, float percentTime, float exclusiveTime, float exclusivePercentTime)
{
	float newRatio = 0.2f;
	float oldRatio = 1.0f - newRatio;

	SProfileSampleHistory* history = NULL;
	bool found = findProfileSampleHistory(name, &history);
	if (found) {
		history->average = (history->average * oldRatio) + (sampleTime * newRatio);
		if (sampleTime < history->min) {
			history->min = sampleTime;
		} else {
			history->min = (history->min * oldRatio) + (sampleTime * newRatio);
		}
		if (sampleTime > history->max) {
			history->max = (history->max * oldRatio) + (sampleTime * newRatio);
		}
		history->percent = percentTime;
		history->exclusive = exclusiveTime;
		history->exclusivePercent = exclusivePercentTime;
	} else if (history) {
		strcpy(history->szName, name);
		history->profileGroup = groupId;
		history->isValid = true;
		history->average = history->min = history->max = sampleTime;
		history->percent = percentTime;
		history->exclusive = exclusiveTime;
		history->exclusivePercent = exclusivePercentTime;
	} else {
		ASSERT(!"Exceeded Max Available Profile Samples");
	}
	return history;
}

void ProfileManager::enableGroup(const char* name)
{
	int groupId = findGroupId(name);
	groupMask |= (1LL << groupId);
}

void ProfileManager::disableGroup(const char* name)
{
	int groupId = findGroupId(name);
	groupMask &= ~(1LL << groupId);
}

int ProfileManager::getProfileSampleCount()
{
	int index = 0;
	while (index < kNumProfileSamples && sSamples[index].isValid) {
		index++;
	}
	return index;
}

SProfileSample* ProfileManager::getProfileSample(int index)
{
	return &sSamples[index];
}

int ProfileManager::getProfileSampleHistoryCount()
{
	int index = 0;
	while (index < kNumProfileSamples && sHistory[index].isValid) {
		index++;
	}
	return index;
}

SProfileSampleHistory* ProfileManager::getProfileSampleHistory(int index)
{
	return &sHistory[index];
}

void ProfileManager::clearTextBuffer()
{
	sOutputBufferSize = 0;
	szOutputBuffer[sOutputBufferSize] = 0;
}

void ProfileManager::putTextBuffer(const char* text)
{
	int len = strlen(text);
	if (len + sOutputBufferSize >= kNumOutputBuffer) {
		ASSERT(!"Exceeded Max Available Profile Output Buffer");
	} else {
#if defined(WIN32)
		strcpy_s(szOutputBuffer + sOutputBufferSize,kNumOutputBuffer-sOutputBufferSize, text);
#else
		strcpy( szOutputBuffer + sOutputBufferSize, text);
#endif		
		sOutputBufferSize += len;
	}
}

void ProfileManager::dumpOutputToBuffer()
{
	if (!enable) {
		return;
	}
	sEndProfileTime = currentMicroSeconds();
	clearTextBuffer();

	const char* szFormat = "%6s : %5s : %5s : %5s : %5s : %5s : %5s : %s\n";
	const char* szFormatSimple = "%6s : %5s : %s\n";
	char szLine[256];
	char szLineSimple[256];

	GX_SPRINTF(szLine, szFormat, "Ave", "Min", "Max", "#", "perc", "exc", "exc.", "Profile Name");
	GX_SPRINTF(szLineSimple, szFormatSimple, "Ave", "#", "Profile Name");

	//putTextBuffer(szLine);
	//putTextBuffer("----------------------------------------------------------------------------\n");
	putTextBuffer(szLineSimple);
	putTextBuffer("-----\n");
	int index = 0;
	while(index < kNumProfileSamples && sSamples[index].isValid) {
		SProfileSample* sample = &sSamples[index];
		if (sample->openProfiles < 0) {
			ASSERT(!"ProfileEnd() called without a ProfileBegin()");
		} else if (sample->openProfiles > 0) {
			ASSERT(!"ProfileBegin() called without a ProfileEnd()");
		}
		float sampleTime = sample->accumulatorTime;
		float percentTime = (sample->accumulatorTime / toSeconds(sEndProfileTime - sStartProfileTime)) * 100.0f;
		float exclusiveTime = sample->accumulatorTime - sample->childrenSampleTime;
		float exclusivePercentTime = (exclusiveTime / toSeconds(sEndProfileTime - sStartProfileTime)) * 100.0f;
		SProfileSampleHistory* history = storeProfileInHistory(sample->profileGroup, sample->szName, sampleTime, percentTime, exclusiveTime, exclusivePercentTime);
		bool isVaild = history && (groupMask & (1LL << history->profileGroup));
		isVaild &= history->average >= numericalFilter;////temp
		if (isVaild) {
			char ave[16], min[16], max[16], num[16], percent[16], exclusive[16], exclusivePercent[16];
			const char* szValueFormat = "%3.1f";

#ifdef _WIN32
			const float kToMillisecond = 1000.f ;//* 5.0f;
#else
			const float kToMillisecond = 1000.f;
#endif
			GX_SPRINTF(ave, szValueFormat, history->average * kToMillisecond);
			GX_SPRINTF(min, szValueFormat, history->min * kToMillisecond);
			GX_SPRINTF(max, szValueFormat, history->max * kToMillisecond);
			GX_SPRINTF(num, szValueFormat, (float)sample->profileInstances);
			GX_SPRINTF(percent, szValueFormat, history->percent);
			GX_SPRINTF(exclusive, szValueFormat, history->exclusive * kToMillisecond);
			GX_SPRINTF(exclusivePercent, szValueFormat, history->exclusivePercent);
			GX_SPRINTF(szLine, szFormat, ave, min, max, num, percent, exclusive, exclusivePercent, history->szName);
			GX_SPRINTF(szLineSimple, szFormatSimple, ave, num, history->szName);
			//putTextBuffer(szLine);
			putTextBuffer(szLineSimple);
		}
		index++;
	}
	//putTextBuffer("----------------------------------------------------------------------------\n");
	putTextBuffer("----\n");
	SProfileSampleHistory* history = storeProfileInHistory(-1, "FPS", 1.0f / toSeconds(sEndProfileTime - sStartProfileTime), 0, 0, 0);
	char ave[16], min[16], max[16];
	const char* szValueFormat = "%3.1f";
	GX_SPRINTF(ave, szValueFormat, history->average);
	GX_SPRINTF(min, szValueFormat, history->min);
	GX_SPRINTF(max, szValueFormat, history->max);
	GX_SPRINTF(szLine, szFormat, ave, min, max, "1.0", "0.0", "0.0", "0.0", history->szName);
	putTextBuffer(szLine);

	resetAll();
	sStartProfileTime = currentMicroSeconds();
}

const char* ProfileManager::getOutputBuffer()
{
	return szOutputBuffer;
}

void ProfileManager::setNumericalFilter(float value)
{
	numericalFilter = value;
}
float ProfileManager::getNumericalFilter() const
{
	return numericalFilter;
}